//********************************************************************************************************
// Product Name: MapWindow.dll Alpha
// Description:  The basic module for MapWindow version 6.0
//********************************************************************************************************
// The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); 
// you may not use this file except in compliance with the License. You may obtain a copy of the License at 
// http://www.mozilla.org/MPL/ 
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF 
// ANY KIND, either expressed or implied. See the License for the specificlanguage governing rights and 
// limitations under the License. 
//
// The Original Code is from MapWindow.dll version 6.0
//
// The Initial Developer of this Original Code is Ted Dunsford. Created 11/25/2008 3:43:56 PM
// 
// Contributor(s): (Open source contributors should list themselves and their modifications here). 
//
//********************************************************************************************************


using System.Collections.Generic;
using MapWindow.Data;
using MapWindow.Geometries;

namespace MapWindow.DirectX
{


    /// <summary>
    /// RasterSequencer
    /// </summary>
    public class RasterSequencer : IEnumerator<Coordinate>
    {
        #region Private Variables

        private readonly IRaster _raster;
        private readonly int _numRows;
        private readonly int _numColumns;
        private readonly bool _needsReproject;
        private readonly IRasterBounds _aff;
        private double[] _prevRow;
        private double[] _curRow;
        private int _pRow;
        private int _pCol;
        private readonly int _numRasterRows;
        private readonly int _numRasterColumns;
        private Coordinate _current;
        private int _row;
        private int _col;
        private VertexModes _vertexMode;
   
        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of RasterSequencer
        /// </summary>
        public RasterSequencer(IRaster raster, int numRows, int numColumns)
        {
            _numRows = numRows;
            _numColumns = numColumns;
            _numRasterColumns = raster.NumColumns;
            _numRasterRows = raster.NumRows;
            _raster = raster;
          
            _row = 0;
            _col = -1;
            _pRow = -1;
            _pCol = -1;
            if (numRows != raster.NumRows || numColumns != raster.NumColumns)
            {
                _needsReproject = true;
                _aff = raster.Bounds.ResampleTransform(numRows, numColumns);
            }
            else
            {
                _aff = new RasterBounds(raster.Bounds.NumRows, raster.Bounds.NumColumns, raster.Bounds.AffineCoefficients);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Does nothing
        /// </summary>
        public void Dispose()
        {

        }

        /// <summary>
        /// Interpolate predicts the value of a point at p based on four neighboring (or regularly spaced)
        /// grid cells.  The top left and bottom right give the geographic grid spacing, while p gives the
        /// relative position of the point to guess the value of.
        /// </summary>
        /// <returns>The interpolated z value</returns>
        static double Interpolate(double[,] Q, double x1, double x2, double y1, double y2, Coordinate p)
        {
            double div = (x2 - x1) * (y2 - y1);
            double a = Q[0, 0] * ((x2 - p.X) * (y2 - p.Y)) / div;
            double b = Q[0, 1] * ((p.X - x1) * (y2 - p.Y)) / div;
            double c = Q[1, 0] * ((x2 - p.X) * (p.Y - y1)) / div;
            double d = Q[1, 1] * ((p.X - x1) * (p.Y - y1)) / div;
            return a + b + c + d;
        }

        /// <summary>
        /// Advances the cell position in raster scan format, setting the current coordinate
        /// to either the interpolated vertex or the cell center.
        /// </summary>
        /// <returns>Boolean, false if there were no more values in the grid.</returns>
        public bool MoveNext()
        {

            _col++;

            if (_vertexMode == VertexModes.NearestNeighbor)
            {
                // Vertices are on cell centers
                if (_col >= _numColumns)
                {
                    _row++;
                    _col = 0;
                }
                if (_row >= _numRows) return false;
                _current = GetNextNNValue();
            }
            else
            {
                // Vertices are on edges and interpolated.
                if (_col > _numColumns)
                {
                    _row++;
                    _col = 0;
                }
                if (_row > _numRows) return false;
                _current = GetNextInterpolatedValue();
            }
            return true;
                        
        }

        private Coordinate GetNextNNValue()
        {
            double z;
            Coordinate p = _aff.CellCenter_ToProj(_row, _col);
            if (_needsReproject)
            {
                RcIndex rc = _aff.ProjToCell(p);
                z = _raster.Value[rc.Row, rc.Column];
            }
            else
            {
                z = _raster.Value[_row, _col];
            }
            return new Coordinate(p.X, p.Y, z);

        }
       

        /// <summary>
        /// Gets the double valued elevation from the specified row and column that correspond
        /// to whatever the new spacing is, regardless of whether it is the original grid
        /// size or not.  This caches values for slightly faster access for interpolation.
        /// </summary>
        /// <param name="row"></param>
        /// <param name="column"></param>
        /// <returns></returns>
        public double GetValue(int row, int column)
        {
            if (row == _pRow && _pRow > 0)
            {
                return _prevRow[column];
            }

            if (row == _pRow + 2)
            {
                // advance to the next row
                _pRow = row - 1;
                _prevRow = _curRow;
                _curRow = new double[_numColumns];
            }

            if (column == _pCol && _pCol > 0)
            {
                return _curRow[column];
            }
           
            if (_needsReproject)
            {
                RcIndex rc = _aff.ProjToCell(_aff.CellCenter_ToProj(row, column));
                if (rc.Column < 0) rc.Column = 0;
                if (rc.Row < 0) rc.Row = 0;
                if (rc.Column > _numRasterColumns) rc.Column = _numRasterColumns;
                if (rc.Row > _numRasterRows) rc.Row = _numRasterRows;
                _curRow[column] = _raster.Value[rc.Row, rc.Column];
            }
            else
            {
                _curRow[column] = _raster.Value[row, column];
                
            }
            _pCol = column;
            return _curRow[column];           
        }

      

        #endregion

        #region Properties

        /// <summary>
        /// Gets the most recently calculated value
        /// </summary>
        public Coordinate Current
        {
            get { return _current; }
        }
        object System.Collections.IEnumerator.Current
        {
            get { return _current; }
        }


        /// <summary>
        /// gets whether or not the specified values will simply use the 
        /// grid index values, or whether a grid position will be calculated.
        /// </summary>
        public bool NeedsReproject
        {
            get { return _needsReproject; }
        }

        /// <summary>
        /// Gets the AffineTransform for this sequencer.
        /// </summary>
        public IRasterBounds Transform
        {
            get { return _aff; }
        }

        #endregion





      

        #region IEnumerator Members

       
       

        private Coordinate GetNextInterpolatedValue()
        {
            int mRow = _row;
            int mCol = _col;
            if (_row == 0)
            {
                mRow = _row + 1;
            }
            if (_col == 0)
            {
                mCol = _col + 1;
            }
            if (_row == _numRows)
            {
                mRow = _row - 1;
            }
            if (_col == _numColumns)
            {
                mCol = _col - 1;
            }
            double [,] Q = new double[2,2];
            Q[0, 0] = GetValue(mRow - 1, mCol - 1);
            Q[0, 1] = GetValue(mRow - 1, mCol);
            Q[1, 0] = GetValue(mRow, mCol - 1);
            Q[1, 1] = GetValue(mRow, mCol);

            Coordinate tl = _aff.CellCenter_ToProj(mRow - 1, mCol - 1);
            Coordinate br = _aff.CellCenter_ToProj(mRow, mCol);

            Coordinate p;
            if (_row != _numRows && _col != _numColumns)
            {
                p = Transform.CellTopLeft_ToProj(_row, _col);
            }
            else if (_row == _numRows && _col != _numColumns)
            {
                p = Transform.CellBottomLeft_ToProj(_row, _col);
            }
            else
            {
                p = Transform.CellBottomRight_ToProj(_row, _col);
            }
            
            double z = Interpolate(Q, tl.X, br.X, tl.Y, br.Y, p);

            return new Coordinate(p.X, p.Y, z);

        }
        /// <summary>
        /// gets the current column
        /// </summary>
        public int Row
        {
            get { return _row; }
        }

        /// <summary>
        /// Gets the current row
        /// </summary>
        public int Column
        {
            get { return _col; }
        }

        /// <summary>
        /// Gets the horizontal texture coordinate from 0 to 1
        /// </summary>
        public float TU
        {
            get { return _col / (float)_numColumns; }
        }

        /// <summary>
        /// Gets the vertical texture coordinate from 0 to 1
        /// </summary>
        public float TV
        {
            get { return _row / (float)_numRows; }
        }

        /// <summary>
        /// Resets the position to the top left of the grid.
        /// </summary>
        public void Reset()
        {
            _col = -1;
            _row = 0;
            _curRow = null;
            _prevRow = null;
        }

        /// <summary>
        /// Gets or sets the vertex modes for this sequencer
        /// </summary>
        public VertexModes VertexMode
        {
            get { return _vertexMode; }
            set 
            {
                _vertexMode = value;
                if (_vertexMode == VertexModes.Bilinear)
                {
                    _curRow = new double[_numColumns + 1];
                }
                
            }
        }

        #endregion
    }
}
